﻿using System;
using System.Xml;
using System.Xml.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Xml.Schema;
using System.Xml.XPath;
using System.IO;
using System.Text;
using System.Xml.Linq;
using System.Linq;
using System.Configuration;

using XmlVisualizer.Controllers;
using XmlVisualizer.Controls;
using XmlVisualizer.Logging;
using XmlVisualizer.DiagramDesigner;
using GuiLabs.Undo;

namespace XmlVisualizer.Models {

	public class ElementChildCollection {

		private Dictionary<XElement, ElementAddress> dictionary;

		public ElementChildCollection() {
			dictionary = new Dictionary<XElement, ElementAddress>();
		}

		public Dictionary<XElement, ElementAddress>.ValueCollection Values {
			get { return dictionary.Values; }
		}

		public void Clear() {
			dictionary.Clear();
		}

		public void Add(XElement key, ElementAddress value) {
			dictionary.Add(key, value);
		}

		public void Remove(XElement key) {
			dictionary.Remove(key);
		}

		public bool ContainsKey(XElement key) {
			return dictionary.ContainsKey(key);
		}

		public ElementAddress this[XElement key] {
			get { return dictionary[key]; }
			set { dictionary[key] = value; }
		}

	}

	public class ElementAddress {
		#region Constructors
		public ElementAddress() {
			this.address = new List<int>();
			this.childNodes = 0;
		}

		public ElementAddress(List<int> address) {
			this.address = address;
		}

		public ElementAddress(int childNodes)
			: this() {
			this.address = new List<int>();
			this.childNodes = childNodes;
		}
		#endregion

		#region Fields
		private int childNodes;
		private List<int> address = null;
		#endregion

		#region Properties
		public int ChildNodes {
			get { return childNodes; }
			set { childNodes = value; }
		}

		public List<int> Address {
			get { return address; }
			set { address = value; }
		}
		#endregion
	}

	public class BigXmlModel : IModel {
		#region Constructors
		public BigXmlModel(XmlModel xmlModel) {
			this.xmlModel = xmlModel;
		}
		#endregion

		#region Fields
		private XmlModel xmlModel;
		private String filename;
		FileStream fs;
		#endregion

		#region Helper methods
		private void FindPosition(ElementAddress address, XmlReader reader) {
			List<int> addressList = address.Address;
			int currentAddress = 0;

			IEnumerator<int> enumerator = addressList.GetEnumerator();
			bool res = enumerator.MoveNext();

			if (res)
				currentAddress = enumerator.Current;
			else
				return;

			int pos = 0;
			bool read = reader.Read();
			while (read) {
				if (reader.NodeType == XmlNodeType.Element) {
					++pos;
					if (pos == currentAddress) {
						res = enumerator.MoveNext();
						if (res) {
							currentAddress = enumerator.Current;
							pos = 0;
						} else
							return;
					} else {
						reader.Skip();
						continue;
					}
				}
				read = reader.Read();
			}
		}

		private XElement ReadXmlChunk(ElementAddress address, string filename, string range, int depth, ElementChildCollection elementChildren) {
			IEnumerable<int> pages = Utils.StringUtils.RangeParsing(range);
			return ReadXmlChunk(address, filename, pages, depth, elementChildren);
		}

		private XElement ReadXmlChunk(ElementAddress address, string filename, IEnumerable<int> childNumbers, int depth, ElementChildCollection elementChildren) {
			XElement element = null; // root element of the returned xml chunk
			XElement currentParent = null; // reference to the current element where to append children - dynamically changes
			int childPosition = 0; // sequence number of the child elements under the root element of this chunk (in the big xml file) 
			Dictionary<int, XElement> parentOnDepth = new Dictionary<int, XElement>(); // dictionary with depth number and corresponding last XElement on that layer
			Dictionary<int, int> depthPosition = new Dictionary<int, int>();

			using (fs = new FileStream(filename, FileMode.Open)) {
				// XmlReader settings
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.ProhibitDtd = false;
				settings.IgnoreWhitespace = true;
				int rootDepth = 0;

				XmlReader xmlReader = null;
				try {
					xmlReader = XmlReader.Create(fs, settings);

					FindPosition(address, xmlReader);
					if (xmlReader.NodeType == XmlNodeType.Element) {
						rootDepth = xmlReader.Depth;
						if (element == null) { // this should be null
							element = CreateElement(xmlReader);
							parentOnDepth[xmlReader.Depth] = element;
							ElementAddress add = new ElementAddress(address.Address);
							elementChildren[element] = add;

							if (childNumbers.Count() == 0) {
								int childNodes = GetNumberOfChildNodes(xmlReader.ReadSubtree());
								elementChildren[element].ChildNodes = childNodes;
							}
						}
					}

					while (xmlReader.Read()) {
						if (xmlReader.NodeType == XmlNodeType.Element) {
							// when we are in the right depth
							if (xmlReader.Depth <= rootDepth + depth && xmlReader.Depth >= rootDepth) {
								if (xmlReader.Depth == rootDepth + 1) {
									++childPosition;
									// check whether this child should be loaded
									if (childNumbers.Contains(childPosition)) {
										XElement childNode = (XElement)ReadChildNode(xmlReader.ReadSubtree(), depth, xmlReader.Depth, elementChildren, address.Address, childPosition);
										parentOnDepth[xmlReader.Depth] = childNode;
										currentParent = parentOnDepth[xmlReader.Depth - 1];
										currentParent.Add(childNode);
									}
									if (childNumbers.Count() > 0 && childPosition > childNumbers.Max()) {
										xmlReader.Skip();
										break;
									}
								}
							} else {
								xmlReader.Skip();
							}

						} else if (xmlReader.NodeType == XmlNodeType.Text) {
							if (xmlReader.Depth <= rootDepth + depth && xmlReader.Depth >= rootDepth) {
								if (xmlReader.Depth == rootDepth + 1) {
									++childPosition;
									//if (childNumbers.Contains(childPosition)) {
									//    element.Add(xmlReader.Value);
									//    XNode textNode = element.LastNode;
									//}
									if (childNumbers.Contains(childPosition)) {
										XNode childNode = ReadChildNode(xmlReader, depth, xmlReader.Depth, elementChildren, address.Address, childPosition);
										if (childNode != null) {
											element.Add(childNode);
										}
									}
								}
							}
						} else if (xmlReader.NodeType == XmlNodeType.EndElement) {
							//break;
						}
						if (xmlReader.Depth <= rootDepth)
							break;
					}
				} catch (Exception ex) {
					Exceptions.ExceptionHandling.HandleException(ex);
				} finally {
					if (xmlReader != null)
						xmlReader.Close();
				}
			}
			return element;
		}

		private XNode ReadChildNode(XmlReader reader, int depth, int currentDepth, ElementChildCollection elementChildren, List<int> parentAddress, int rootAddress) {
			Dictionary<int, XElement> parentOnDepth = new Dictionary<int, XElement>();
			Dictionary<int, int> depthPositions = new Dictionary<int, int>();

			List<int> currentAddress = new List<int>();
			foreach (int i in parentAddress)
				currentAddress.Add(i);

			XElement root = null;

			if (reader.NodeType == XmlNodeType.Text) {
				XText textNode = new XText(reader.Value);
				return textNode;
			}

			currentDepth = reader.Depth;
			while (reader.Read()) {
				if (reader.Depth < currentDepth) {
					currentAddress.RemoveAt(currentAddress.Count - 1);
					currentDepth = reader.Depth;
				} else if (reader.Depth > currentDepth) {
					currentDepth = reader.Depth;
				}
				if (reader.Depth < depth && reader.Depth >= 0) {
					if (reader.NodeType == XmlNodeType.Element) {
						if (depthPositions.ContainsKey(reader.Depth))
							depthPositions[reader.Depth]++;
						else
							depthPositions[reader.Depth] = 1;

						XElement element = CreateElement(reader);

						List<int> add = new List<int>();
						foreach (int i in currentAddress) {
							add.Add(i);
						}
						if (currentDepth > 0)
							add.Add(depthPositions[reader.Depth]);
						else
							add.Add(rootAddress);
						elementChildren[element] = new ElementAddress(add);

						if (reader.Depth == 0) {
							root = element;
							parentOnDepth[reader.Depth] = root;
						} else {
							XElement parent = parentOnDepth[reader.Depth - 1];
							parent.Add(element);
							parentOnDepth[reader.Depth] = element;
						}

						if (reader.Depth == depth - 1) {
							int childNodes = GetNumberOfChildNodes(reader.ReadSubtree());
							elementChildren[element].ChildNodes = childNodes;
						}
					} else if (reader.NodeType == XmlNodeType.Text) {
						XElement parent = parentOnDepth[reader.Depth - 1];
						parent.Add(reader.Value);
					}
				} else if (reader.Depth >= depth) {
					reader.Skip();
				}
			}
			return root;
		}

		private int GetNumberOfChildNodes(XmlReader reader) {
			int currentDepth = reader.Depth;
			int nodes = 0;
			while (reader.Read()) {
				if (reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.Text) {
					if (reader.Depth == currentDepth + 1)
						++nodes;
					else if (reader.Depth > currentDepth + 1)
						reader.Skip();
				}
			}
			return nodes;
		}

		/// <summary>
		/// Creates new XElement from actual position in XmlReader.
		/// </summary>
		/// <param name="reader">XmlReader</param>
		/// <returns>new XElement</returns>
		/// <remarks>XmlReader is changed by this method, it reads from it and therefore the current position is different.</remarks>
		private XElement CreateElement(XmlReader reader) {
			string localName = reader.LocalName;
			string namespaceName = reader.NamespaceURI;
			string prefix = reader.Prefix;
			XNamespace ns = namespaceName;
			XElement el = new XElement(ns.GetName(localName));

			if (reader.HasAttributes) {
				reader.MoveToFirstAttribute();
				el.SetAttributeValue(reader.Name, reader.Value);
				while (reader.MoveToNextAttribute()) {
					el.SetAttributeValue(reader.Name, reader.Value);
				}
				reader.MoveToElement();
			}
			return el;
		}
		#endregion

		#region IModel Members
		public void OpenFile(string filename) {
			this.filename = filename;
			ElementChildCollection elementChildren = new ElementChildCollection();
			List<int> rootAddress = new List<int>();
			rootAddress.Add(1);
			XElement element = ReadXmlChunk(new ElementAddress(rootAddress), filename, "1-10", 1, elementChildren);
			xmlModel.LoadXml(element, elementChildren);
		}

		public void SaveFile(string filename) {
			throw new NotImplementedException();
		}

		public string GetXmlText() {
			return xmlModel.GetXmlText();
		}

		public void Close() {
			throw new NotImplementedException();
		}

		public void Expand(XElement element, ElementAddress address, String range, int depth) {
			ElementChildCollection elementChildren = new ElementChildCollection();
			XElement root = ReadXmlChunk(address, filename, range, depth, elementChildren);

			foreach (XNode node in root.Nodes()) {
				if (node is XElement) {
					XElement el = node as XElement;
					XElement newEl = new XElement(el);
					ElementAddress addr = elementChildren[el];
					XNodeInfo info = new XNodeInfo(new Point(0, 0), 0, 0, Properties.Settings.Default.defaultAppearance, newEl);
					info.ElementInfo = addr;
					xmlModel.AddElementAddress(newEl, addr);
					xmlModel.AddNewElement(element, info);
				} else if (node is XText) {
					XText text = new XText(node as XText);
					XNodeInfo info = new XNodeInfo(new Point(0, 0), 0, 0, Properties.Settings.Default.defaultAppearance, text);
					xmlModel.AddNewElement(element, info);
				}
			}
		}
		#endregion
	}
}